#include <vector>
#include <map>
#include <cstdio>
#include <algorithm>

#include "Layout.h"
#include "BRIC.h"

#include <math.h>

using namespace std;

Layout::Layout(Node* root, int width, int height, int alpha, int beta) {
    setWidth(width);
    setHeight(height);
    setAlpha(alpha);
    setBeta(beta);
    setRoot(root);
}

Layout::Layout(vector<Image*> *images, int width, int height, int alpha, int beta) {
    setWidth(width);
    setHeight(height);
    setAlpha(alpha);
    setBeta(beta);
    this->setRoot(this->createTree(images));
}

Layout::Layout(Layout* layout) {
    setWidth(layout->getWidth());
    setHeight(layout->getHeight());
    setAlpha(layout->getAlpha());
    setBeta(layout->getBeta());
    setRoot(new Node(layout->getRoot()));
}

void Layout::setRoot(Node* root) {
    this->root = root;
}

void Layout::setWidth(int width) {
    this->width = width;
}

void Layout::setHeight(int height) {
    this->height = height;
}

void Layout::setAlpha(int alpha) {
    this->alpha = alpha;
}

void Layout::setBeta(int beta) {
    this->beta = beta;
}

int Layout::getWidth() {
    return this->width;
}

int Layout::getHeight() {
    return this->height;
}

int Layout::getAlpha() {
    return this->alpha;
}

int Layout::getBeta() {
    return this->beta;
}

int Layout::getTotalArea() {
    return this->getTotalDimension(HORIZONTAL) * this->getTotalDimension(VERTICAL);
}

Node* Layout::getRoot() {
    return this->root;
}

int Layout::getArea() {
    return this->getWidth() * this->getHeight();
}

void imprimirArvore0(Node* raiz) {
    if(raiz->getType() == BISS) {
        printf("(");
        imprimirArvore0(raiz->getLeft());
        printf("[%s]", raiz->getOrientation() == HORIZONTAL? "H" : "V");
        imprimirArvore0(raiz->getRight());
        printf(")&%d", raiz);
    } else {
        Image* image = raiz->getImage();
        printf("[%d(%.2f)%d]&%d", image->getWidth(), image->getRelativeArea(), image->getHeight(), raiz);
    }
}

void imprimirArvore1(Node* raiz) {
    if(raiz->getType() == BISS) {
        printf("(");
        imprimirArvore1(raiz->getLeft());
        printf("[%s]", raiz->getOrientation() == HORIZONTAL? "H" : "V");
        imprimirArvore1(raiz->getRight());
        printf(")");
    } else {
        Image* image = raiz->getImage();
        printf("[%d(%.2f)%d]", image->getWidth(), image->getRelativeArea(), image->getHeight());
    }
}

void calculateAspectAndRArea(Node* node, map<Node*, double> *eNode, map<Node*, double> *aNode) {
    if(node->isLeaf()) {
        eNode->insert(pair<Node*, double>(node, node->getImage()->getRelativeArea()));
        aNode->insert(pair<Node*, double>(node, node->getImage()->getAspectRatio()));
    } else {
        calculateAspectAndRArea(node->getLeft(), eNode, aNode);
        calculateAspectAndRArea(node->getRight(), eNode, aNode);
        double leftRelativeHeight = sqrt(aNode->at(node->getLeft()) * eNode->at(node->getLeft()));
        double rightRelativeHeight = sqrt(aNode->at(node->getRight()) * eNode->at(node->getRight()));
        double leftRelativeWidth = sqrt(eNode->at(node->getLeft()) / aNode->at(node->getLeft()));
        double rightRelativeWidth = sqrt(eNode->at(node->getRight()) / aNode->at(node->getRight()));
        double totalRelativeHeight = leftRelativeHeight + rightRelativeHeight;
        double totalRelativeWidth = leftRelativeWidth + rightRelativeWidth;
        if(node->getOrientation() == VERTICAL) {
            double maxRelativeHeight = max(leftRelativeHeight, rightRelativeHeight);
            double eVal = maxRelativeHeight * totalRelativeWidth;
            double aVal = maxRelativeHeight / totalRelativeWidth;
//            double aVal = totalRelativeWidth / maxRelativeHeight;
            eNode->insert(pair<Node*, double>(node, eVal));
            aNode->insert(pair<Node*, double>(node, aVal));
        } else {
            double maxRelativeWidth = max(leftRelativeWidth, rightRelativeWidth);
            double eVal = maxRelativeWidth * totalRelativeHeight;
            double aVal = totalRelativeHeight / maxRelativeWidth;
//            double aVal = maxRelativeWidth / totalRelativeHeight;
            eNode->insert(pair<Node*, double>(node, eVal));
            aNode->insert(pair<Node*, double>(node, aVal));            
        }
    }
}

/*
 Returns given node's relative area and aspect ratio, respectively
 */
pair<double, double>* nodeEA(Node* node) {
    pair<double, double>* EA;
    if(node->isLeaf()) {
        EA = new pair<double, double>(node->getImage()->getRelativeArea(), node->getImage()->getAspectRatio());
    } else {
        pair<double, double>* leftEA = nodeEA(node->getLeft());
        pair<double, double>* rightEA = nodeEA(node->getRight());
        double relativeArea;
        double aspectRatio;
        if(node->getOrientation() == VERTICAL) {
            double maxRelativeHeight = max(sqrt(leftEA->first*leftEA->second), sqrt(rightEA->first*rightEA->second));
            double totalRelativeWidth = sqrt(leftEA->first / leftEA->second) + sqrt(rightEA->first / rightEA->second);
            relativeArea = maxRelativeHeight * totalRelativeWidth;
            aspectRatio = maxRelativeHeight / totalRelativeWidth;
        } else {
            double totalRelativeHeight = sqrt(leftEA->first * leftEA->second) + sqrt(rightEA->first * rightEA->second);
            double maxRelativeWidth = max(sqrt(leftEA->first / leftEA->second), sqrt(rightEA->first / rightEA->second));
            relativeArea = maxRelativeWidth * totalRelativeHeight;
            aspectRatio = totalRelativeHeight / maxRelativeWidth;
        }
        EA = new pair<double, double>(relativeArea, aspectRatio);
    }
    return EA;
}

double sumE(Node* node) {
    return node->isLeaf() ? node->getImage()->getRelativeArea() : (sumE(node->getLeft()) + sumE(node->getRight()));
}

double Layout::score2004(Node* node) {
    map<Node*, double> eNode;
    map<Node*, double> aNode;
    double aPage = ((double)(this->getHeight())) / ((double)(this->getWidth()));
    pair<double, double>* EA = nodeEA(node);
    double propE = (sumE(node) / EA->first);
    double propA = (min(EA->second, aPage) / max(EA->second, aPage));
    return propE * propA;
}

double idealArea(Image* image, double eSum, double area) {
    return area*(image->getRelativeArea() / eSum);
}

double occupiedArea(Node* node, double totalArea) {
    if(node->isLeaf()) {
        
    } else {
        
    }
}

double Layout::score2008(Node* node) {
    double eSum = sumE(node);
    double area = occupiedArea(node, this->getArea());
    return eSum;
}

void fromRootPath(Node* node, vector<Direction> *path) {
    Node* actual = node;
    while(actual->getFather() != NULL) {
        path->push_back(actual->getFather()->getLeft() == actual ? LEFT : RIGHT);
        actual = actual->getFather();
    }
}

Node* Layout::getMax(vector<Node*> *trees) {
    Node* max = trees->at(0);
    for(int i = 1; i < trees->size(); i++) {
        if(this->score2004(trees->at(i)) > this->score2004(max))
            max = trees->at(i);
    }
    return max;
}

void Layout::generateTree(Node* root, Node* paramActual, Node* leaf, vector<Node* >* scores, Orientation orientation) {
    Node* copy = new Node(root);
    vector<Direction> path;
    fromRootPath(paramActual, &path);
    Node* actual = copy;
    while(!path.empty()) {
        actual = path.back() == LEFT ? actual->getLeft() : actual->getRight();
        path.pop_back();
    }
    Node* father = actual->getFather();
    Node* newNode = new Node(actual, orientation, new Node(leaf));
    if(father) {
        if(father->getLeft() == actual)
            father->setLeft(newNode);
        else
            father->setRight(newNode);
        scores->push_back(copy);
    } else {
        scores->push_back(newNode);
    }
}

void Layout::generateTrees(Node* root, Node* paramActual, Node* leaf, vector<Node* >* trees) {
    generateTree(root, paramActual, leaf, trees, HORIZONTAL);
    generateTree(root, paramActual, leaf, trees, VERTICAL);
    if(!paramActual->isLeaf()) {
        this->generateTrees(root, paramActual->getLeft(), leaf, trees);
        this->generateTrees(root, paramActual->getRight(), leaf, trees);
    }
}

Node* Layout::createTree(vector<Image*> *images) {
    vector<Node*> leafList;
    for(int i = 0; i < images->size(); i++) {
        leafList.push_back(new Node(images->at(i)));
        leafList.at(i)->setId(i);
    }
    Node* root = leafList.at(0);
    for(int i = 1; i < leafList.size(); i++) {
        vector<Node*> trees;
        this->generateTrees(root, root, leafList.at(i), &trees);
        root = this->getMax(&trees);
    }
    return root;
}

int Layout::getTotalDimension(Orientation orientation) {
    int totalDimension = 0;
    vector<Node*> list;
    list.push_back(this->getRoot());
    while(!list.empty()) {
        Node* actual = list.at(0);
        if(actual->isLeaf()) {
            totalDimension += (orientation == HORIZONTAL ? actual->getImage()->getWidth() : actual->getImage()->getHeight()) + 2*this->getBeta();
        } else {
            if(actual->getOrientation() == orientation) {
                list.push_back(actual->getLeft());
            } else {
                list.push_back(actual->getLeft());
                list.push_back(actual->getRight());
                totalDimension += this->getAlpha();
            }
        }
        list.erase(list.begin());
    }
    return totalDimension;
}
